//
// Ephi - simulation of magnetic fields and particles
// Copyright (C) 2007 Indrek Mandre <indrek(at)mare.ee>
// For more information please see http://www.mare.ee/indrek/ephi/
//
// This program is free software; you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation; either version 2 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License along
// with this program; if not, write to the Free Software Foundation, Inc.,
// 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
//

#ifndef __screen_hpp__
#define __screen_hpp__

#include <string>

#include "math3d.hpp"

/** RGB */
struct color3
{
  color3() { }
  color3(const vect3d& v) :
      b((unsigned char)prec2double(v.z * 255)),
      g((unsigned char)prec2double(v.y * 255)),
      r((unsigned char)prec2double(v.x * 255)) { }
  color3(unsigned char r, unsigned char g, unsigned char b) : b(b), g(g), r(r) { }

  friend bool operator== (const color3&l, const color3&r) { return l.r == r.r && l.g == r.g && l.b == r.b; }
  friend bool operator!= (const color3&l, const color3&r) { return !(l == r); }

  unsigned char b, g, r;
} ATTRIBUTE_PACKED;

/** Screen is a camera. */
class Screen
{
public:
  typedef color3 color;

  static const color WHITE;
  static const color RED;
  static const color GREEN;
  static const color BLUE;
  static const color BLACK;

  /** Create screen.
    * @param w Width of screen in pixels
    * @param h Height of screen in pixels
    * @param sw Horizontal scale of screen (meters)
    * @param sh Vertical scale of screen (meters)
    */
  Screen (size_t w, size_t h, prec_t sw, prec_t sh, const color& bg = WHITE);
  Screen (const Screen& copy);
  ~Screen ();

  Screen& operator= (const Screen& copy);

  void clear(const color& c = WHITE);

  /** Draw axes onto screen based on the scales. */
  void draw_axes (prec_t interval = 0.0, const color& c = BLUE);

  /** Set pixel at raster position (x, y) on screen. */
  void set_pixel (size_t x, size_t y, const color& c);
  /** Set pixel on screen at given position p. The position is translated into raster coordinates. */
  void set_pixel (const vect3d& p, const color& c);
  /** @deprecated */
  void set_pixel_merge (size_t x, size_t y, prec_t factor, const color& c);

  /** Translate given position p into raster coordinate (out_x, out_y). */
  void translate (const vect3d& p, size_t& out_x, size_t& out_y);
  /** Translate given position p into scaled coordinate (x, y) and distance from screen out_z. */
  void translate (const vect3d& p, prec_t& out_x, prec_t& out_y, prec_t& out_z);
  /** Translate raster coordinate (x, y) into space coordinate out_p. */
  void reverse (size_t x, size_t y, vect3d& out_p);

  prec_t get_dx () { return dx; }
  prec_t get_dy () { return dy; }
  prec_t get_dw () { return dx; }
  prec_t get_dh () { return dy; }

  /** Write screen as window bitmap into given path. */
  void write (const char *path);

  /** Write screen as window bitmap into given path. */
  void write (const std::string& path) { write (path.c_str()); }

  prec_t get_space_width () const { return space_width; }
  prec_t get_space_height () const { return space_height; }

  size_t get_width () const { return width; }
  size_t get_height () const { return height; }

  enum eplane { UNDEFINED = 0, PLANE_XY = 1, PLANE_YZ, PLANE_ZX };
  /** Set axes of the camera to match the given plane. */
  void set_axis (eplane plane, prec_t distance = 0.0);
  /** Set axes of the camera. */
  void set_axis (const vect3d& pos0, const vect3d& xaxix, const vect3d& yaxis);
  /** Set screen into perspective mode. */
  void set_perspective (bool p) { is_perspective = p; }
  /** Enable zbuffer. */
  void set_zbuffer (bool enable);

  /** Draw a generic aliased line. */
  void draw_line (const vect3d& p0, const vect3d& p1, const color& c);

  /** Draw a cross across all axes. */
  void draw_cross (const vect3d& p0, const color& c, prec_t len = 0.05);

  const vect3d& get_center () const { return center; }

private:
  size_t width, height;
  prec_t space_width, space_height;
  prec_t dw, dh;
  prec_t dx, dy;
  color *smap;
  prec_t *zbuf;
  vect3d center, normal, xdir, ydir;
  prec_t plane_d;
  eplane plane_type;
  bool is_perspective;

  void set_pixel_prec (prec_t x, prec_t y, const color& c);
};

#endif // !__screen_hpp__

